# --------------------------------------------------------------------------------
#  NoiseTube Web application
#  
#  Copyright (C) 2008-2010 SONY Computer Science Laboratory Paris
#  Portions contributed by Vrije Universiteit Brussel (BrusSense team), 2008-2011
# --------------------------------------------------------------------------------
#  This library is free software; you can redistribute it and/or modify it under
#  the terms of the GNU Lesser General Public License, version 2.1, as published
#  by the Free Software Foundation.
#  
#  This library is distributed in the hope that it will be useful, but WITHOUT
#  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
#  details.
#  
#  You should have received a copy of the GNU Lesser General Public License along
#  with this library; if not, write to:
#    Free Software Foundation, Inc.,
#    51 Franklin Street, Fifth Floor,
#    Boston, MA  02110-1301, USA.
#  
#  Full GNU LGPL v2.1 text: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
#  NoiseTube project source code repository: http://code.google.com/p/noisetube
# --------------------------------------------------------------------------------
#  More information:
#   - NoiseTube project website: http://www.noisetube.net
#   - Sony Computer Science Laboratory Paris: http://csl.sony.fr
#   - VUB BrusSense team: http://www.brussense.be
# --------------------------------------------------------------------------------

class CitiesController < ApplicationController

  require 'maps/process_maps'
  require 'json'
  require 'shapefile'

  #caches_action :index, :layout=>false
  #caches_page :index

  respond_to :xml, :json, :html, :rss, :kml, :kmz, :zip

  before_filter :admin_check, :only => [:iscopecities]

  def index
    #render :file => File.join( Rails.root, 'public', 'cache/cities.html')
    index_slow
  end

  def index_slow
    @best_cities = []
    @cities = City.with_geodata
    stored_cities = City.best_cities(4)
    stored_cities.each{|city|
      contributors=[]
      city.contributors(5).each{|track| contributors<<[track.user, track.last] }
      @best_cities<<{
          :city =>city,
          :tags=>city.tags(10),
          :contributors=>contributors,
          :has_map=>File.exist?("#{Rails.root}/public/kmz/city_#{city.id}.kmz")
      }
    }
  end

  def index_fast
    @cities = []
    stored_cities = City.with_geodata
    stored_cities.each{|city|
      contributors=[]
      city.contributors(5).each{|track| contributors<<[track.user, track.last] }
      @cities<<{
          :city =>city,
          :tags=>city.tags(10),
          :contributors=>contributors,
          :has_map=>File.exist?("#{Rails.root}/public/kmz/city_#{city.id}.kmz")
      }
    }
    @cities.sort!{|a,b| b[:city].measures_size<=>a[:city].measures_size}
    @best_cities=@cities[0..4]
  end

  def show
      @city = City.find(params[:id])
      @onload="initialize()"
      @onunload="GUnload()"

      respond_to do |format|
      format.html {
              #send_file("#{RAILS_ROOT}/public/kmz/city_#{@city.id}.kmz", {:type=>"application/vnd.google-earth.kmz"})
      }
      format.kmz {
        send_file "#{Rails.root}/public/kmz/city_#{@city.id}.kmz"
      }
      format.rss {
        @tracks=@city.tracks.all(:conditions=>"tracks.processed = true and (tracks.public is null or tracks.public = true) and tracks.geolocated=true", :order=>"tracks.created_at desc", :limit=>20 )
        render :rss =>  @tracks
      }
      format.kml {
        as_kml
      }
    end
  end

  # kenk = 17/06/2013
  # iscopecities: gets a list of iscopecities for the logged-in user
  def iscopecities
    @city_data = []

    @city = @user.city

    @tracks = @city.tracks.paginate(:conditions=>"public = true", :per_page => 10, :order=>"tracks.created_at desc", :page => params[:page])


    contributors=[]
    @city.contributors(5).each{|track| contributors<<[track.user, track.last] }
    @city_data<<{
        :city =>@city,
        :tags=>@city.tags(10),
        :contributors=>contributors,
        :has_map=>File.exist?("#{Rails.root}/public/kmz/city_#{@city.id}.kmz")
    }
  end

  def querycity
    @city = City.find(params[:id])
    #@city_users = User.where("city_id=#{@city.id}")  #NOTE: There are also users through tracks
    @city_track_users = User.joins(:tracks).where("tracks.city_id=#{@city.id}").order("users.login ASC").uniq.all
    #@city_tracks = Track.where("city_id=#{@city.id}")

    #Tracks.all.each { | track|
    #  if (track.city.nil?)
    #    track.find_location
    #  end
    #}
  end

  def aggregatemaps
    respond_to do |format|
      format.html {
        #render html
      }
    end
  end

  # ------ custom maps page -------
  def custommaps

    if current_user.nil?
      return redirect_to "/cities"
    end

    @user = current_user
    respond_to do |format|
      format.html {
        #get user's maps
        @aggmaps = AggregateMap.paginate(:conditions=> "aggregate_maps.user_id = #{@user.id}",:per_page => 7, :order=>"aggregate_maps.created_at desc", :page => params[:page])
      }
      format.kml  {
        custommaps_kml
      }
      format.json{
        custommaps_json
      }
      format.zip{
        #get the aggmap
        map = AggregateMap.find(params[:id])
        filename = SHAPE_FILES_PATH + Shapefile::create_filename_for_map(map) + ".zip"
        # old name: "#{SHAPE_FILES_PATH}Shapefile_#{params[:id]}.zip"
        if File.exist? filename
          send_file filename, :type=>"application/zip" #, :x_sendfile=>true streams
        else
          if map.comments =~ /no measures/
            flash[:error] = "Cannot download file. There were no measures found for that map."
          else
            flash[:error] = "Cannot download file. Please try again later."
          end
          redirect_to :controller => "cities", :action => "custommaps"
        end
      }
    end
  end

  #sends custommaps data as json. builds whole string and sends it with contentype json
  def custommaps_json

    aggmap_id = params[:id].gsub(/aggmap-/, "").to_i unless params[:id].nil?

    aggmap = AggregateMap.find(aggmap_id)
    aggmap_json = aggmap.json_text

    aggmap_json = "[]" if aggmap.json_text.blank?

    render :text => aggmap_json,
           status: "200 OK"
  end

  # --- builds cusom maps kml. 1st time generates the kml (might take some 15secs)
  # subsequent times retrieves from shared folder for customaps kml
  def custommaps_kml
    @baseurl=root_url
    @city_url="/cities/custommaps"

    @aggmap_id = params[:id].gsub(/aggmap-/, "").to_i unless params[:id].nil?

    @aggmap = AggregateMap.find(@aggmap_id)

    aggmap_filename = "#{AGGMAPS_PATH}noisetubemap_#{@aggmap.id}.kml"

    #check if file exists, if so send it
    if File.exists?(aggmap_filename)
        send_file aggmap_filename, :type => :kml, :x_sendfile => true
    else
      #generate object from json
      aggmap_objects = JSON.parse(@aggmap.json_text)

      unless aggmap_objects.blank?
        if aggmap_objects.try(:size) > 0
          #remove those without measures
          @aggmap_with_measures = aggmap_objects.select { |m| !m["measures"].blank? }
          measures_json= @aggmap_with_measures.collect { |elm| elm["measures"] unless elm["measures"].nil? }.reject(&:empty?)

          measures_json = measures_json.flatten! unless measures_json.nil?

          @ranges = Measure.by_range_json(measures_json)

          #@users=@aggmap.city.users #note - more users could be linked with track cities that are not here
          @tags_measure={}
          #todo: Tags
          #Measure.tag_counts(:order=>"tag_name DESC").each{ |tag|
          #taggings=tag.taggings.find(:all, :include=>[:taggable, :tagger], :joins=>"LEFT OUTER JOIN measures ON taggings.taggable_id = measures.id LEFT OUTER JOIN tracks ON measures.track_id = tracks.id", :conditions=>"measures.loudness is not null and measures.geom is not null and tracks.city_id=#{@city.id} and taggings.context='tags'")


          stream = render_to_string(:template => "cities/custommaps", handlers: [:erb], format: [:kml])

          #save to file so that we dont do this again
          file = File.new(aggmap_filename, "w")
          file.write(stream)
          file.close

          #set the cookes for jquery dload plugin
          cookies['fileDownload'] = 'true'

          #now send it
          send_data(stream, :mimetype => :kml, :type => 'content-type', :filename => "noisetubemap_#{@aggmap_id}.kml")

        end
      end
    end
  end


  # ----- called from AJAX ----
  def runquery
    #selected tracks
    #query_tracks = params[:track_id]

    #selected users
    results = runquery_helper()

    render :json=>results.to_json

  end

  # generate aggregated maps from params - called from ajax in querycity
  def aggmap

    query_users = params[:user][:id]

    #bounding box
    query_bbox_br = params[:geobr]
    query_bbox_tl = params[:geotl]

    #times
    query_start_time = params[:starttime]
    query_end_time = params[:endtime]

    #db values
    query_start_db = params[:dbstart]
    query_end_db = params[:dbend]

    # now build the query

    #get users measurements
    measures = execquerymeasures(query_users, query_start_db, query_end_db, query_bbox_br, query_bbox_tl, query_start_time, query_end_time)

    result = ProcessMaps.aggregatedmap(measures)

    render :json=>result, :status => "200 OK"
  end

  # generate aggregated maps from parameters
  # in aggregatemaps.html.erb form. takes a long time to execute, a delayed version
  # follows after this one
  def aggcitymeasures
    query_city = params[:thecity]

    #times
    query_start_time = params[:starttime]
    query_end_time = params[:endtime]
    # now build the query

    #get users measurements
    measures = execcitymeasures(query_city, query_start_time, query_end_time)

    result = ProcessMaps.aggregatedmap(measures)

    render :json=>result, :status => "200 OK"
  end

  # generate aggregated maps from params in custommaps.erb view
  # this is the backend delayed job version
  def aggcitymeasures_delayed
    #name
    map_name = params[:mapname]
    #remove whitespace
    map_name.gsub(/\s+/, '_')
    #times
    query_start_time = params[:starttime]
    query_end_time = params[:endtime]
    #bounding box
    query_bbox_br = params[:geobr]
    query_bbox_tl = params[:geotl]
    #get location if possible

    if query_start_time.blank? or query_end_time.blank?
      flash[:error] = t 'cities.iscope.aggmaps.choosecity'
      redirect_to '/cities/custommaps.html'
    else
      if query_bbox_tl=~ /(.*),(.*)/
        tl_lng = $2.to_f
        tl_lat = $1.to_f
      end
      if query_bbox_br=~ /(.*),(.*)/
        br_lng = $2.to_f
        br_lat = $1.to_f
      end
      # now build the query

      #create bounding_box

      #create a new AggregatedMap
      map = AggregateMap.new(:name=>map_name,:user => current_user, :start_time=> DateTime.strptime(query_start_time, DATE_TIME_FORMAT),
                             :end_time => DateTime.strptime(query_end_time, DATE_TIME_FORMAT))
      #set bbox
      map.set_bounding_box(tl_lat,tl_lng,br_lat,br_lng)

      if map.end_time < map.start_time
        flash[:error] = t 'cities.iscope.aggmaps.timesareinvalid'
        return redirect_to '/cities/custommaps.html'
      end
      #iscope admins can request maps with longer duration
      unless iscopeadmin?
        if ((map.end_time - map.start_time) / 1.day) > 358
          flash[:error] = t 'cities.iscope.aggmaps.timesexceedrange'
           return redirect_to '/cities/custommaps.html'
        end
      end

      
      #save for processing later
      map.save
      flash[:notice] = t 'cities.iscope.aggmaps.requestqueued'
      redirect_to '/cities/custommaps.html'
    end

  end
  #-----------------------------------------------------

  private
  #checks if a user has authentication for some pages
  def admin_check
    if current_user.nil? or (current_user.role != "iscopeadmin" and current_user.role != "admin")
      redirect_to root_url, :notice => (t 'messages.security.notauthorized')
    end
    @user = current_user
  end


  #helper to run query for track data from iscopecities page
  def runquery_helper
    query_users = params[:user][:id]

    #bounding box
    query_bbox_br = params[:geobr]
    query_bbox_tl = params[:geotl]

    #times
    query_start_time = params[:starttime]
    query_end_time = params[:endtime]

    #db values
    query_start_db = params[:dbstart]
    query_end_db = params[:dbend]

    # now build the query

    #get users measurements
    results = execquery(query_users, query_start_db, query_end_db, query_bbox_br, query_bbox_tl, query_start_time, query_end_time)
  end

  # ------ old stuff -------
  def as_kml

    @baseurl=root_url
    @city_url=city_url

    if (params[:dl]=="1")
      # legend part
      kml_legend
    else

      @ranges=Measure.by_range(@city.measures)
      @users=@city.users
      @tags_measure={}

      Measure.tag_counts(:order=>"tag_name DESC").each{ |tag|
        #taggings=tag.taggings.find(:all, :include=>[:taggable, :tagger], :joins=>"LEFT OUTER JOIN measures ON taggings.taggable_id = measures.id LEFT OUTER JOIN tracks ON measures.track_id = tracks.id", :conditions=>"measures.loudness is not null and measures.geom is not null and tracks.city_id=#{@city.id} and taggings.context='tags'")

        unless taggings.empty?
          if @tags_measure[tag.tag_name].nil?
            @tags_measure[tag.tag_name]={:taggings=>[], :count=>0}
          end
          @tags_measure[tag.tag_name][:taggings]+=tag
          @tags_measure[tag.tag_name][:count]+=1
        end
      }

      render :template => "cities/map.kml.erb", :mimetype => :kml
    end
  end

  def kml_legend
    @box=params[:BBOX]
    @tag_type=params[:tagtype] || "tags"
    @telematin=(params[:telematin].nil?)? "" : "&telematin=1"
    render :template=>"cities/map_dynamic_legend.kml.erb", :mimetype => :kml
  end

  # --------- execquery for aggmaps
  def execquery(query_users, query_start_db, query_end_db, query_bbox_br, query_bbox_tl, query_start_time, query_end_time)
    #remove empties
    query_users.reject!(&:empty?) unless query_users.nil?

    # get the users
    users = User.find(query_users)

    #Track.where("users_id in (?) ", query_users)

    query_result = []

    #get location if possible
    if query_bbox_tl=~ /(.*),(.*)/
      tl_lng = $2.to_f
      tl_lat = $1.to_f
    end
    if query_bbox_br=~ /(.*),(.*)/
      br_lng = $2.to_f
      br_lat = $1.to_f
    end



    users.each{ |user|
      #the tracks
      user_tracks = user.tracks

      user_tracks.each{|user_track|
        #geo
        user_track_measures = user_track.measures.scoped

        # WHERE mytable.geom && ST_MakeEnvelope(minLon, minLat, maxLon, maxLat, 4326);
        user_track_measures = user_track_measures.where(" measures.geom && ST_MakeEnvelope(?, ?, ?, ?, ?)", tl_lng,tl_lat, br_lng, br_lat, GeoFeatures::PROJECTION_SRID) unless query_bbox_tl.blank?
        #time
        user_track_measures = user_track_measures.where(" measures.made_at >= ? ", query_start_time)  unless query_start_time.blank?
        user_track_measures = user_track_measures.where(" measures.made_at <= ? ", query_end_time)  unless query_end_time.blank?
        #db
        user_track_measures = user_track_measures.where(" measures.loudness >= ?", query_start_db) unless query_start_db.blank?
        user_track_measures = user_track_measures.where(" measures.loudness <= ?", query_end_db) unless query_end_db.blank?

        query_result << {user: user, measures: user_track_measures} #can add tracks
      }

      #.map(&:inspect).join(', ')
    } unless users.blank?

    query_result

  end

  # same as exec query, but only returns measures
  def execquerymeasures(query_users, query_start_db, query_end_db, query_bbox_br, query_bbox_tl, query_start_time, query_end_time)
    #remove empties
    query_users.reject!(&:empty?) unless query_users.nil?

    # get the users
    users = User.find(query_users)

    #Track.where("users_id in (?) ", query_users)

    query_result = []

    #get location if possible
    if query_bbox_tl=~ /(.*),(.*)/
      tl_lng = $2.to_f
      tl_lat = $1.to_f
    end
    if query_bbox_br=~ /(.*),(.*)/
      br_lng = $2.to_f
      br_lat = $1.to_f
    end

    startt = DateTime.strptime(query_start_time, DATE_TIME_FORMAT)  unless query_start_time.blank?
    endt = DateTime.strptime(querY_end_time, DATE_TIME_FORMAT) unless query_end_time.blank?

    users.each{ |user|
      #the tracks
      user_tracks = user.tracks

      user_tracks.each{|user_track|
        #geo
        user_track_measures = user_track.measures.scoped

        # WHERE mytable.geom && ST_MakeEnvelope(minLon, minLat, maxLon, maxLat, 4326);
        user_track_measures = user_track_measures.where(" measures.geom is not null and (measures.geom && ST_MakeEnvelope(?, ?, ?, ?, ?) )", tl_lng,tl_lat, br_lng, br_lat, GeoFeatures::PROJECTION_SRID) unless query_bbox_tl.blank?
        #time
        user_track_measures = user_track_measures.where(" measures.made_at >= ? ", startt.to_s(:db))  unless startt.blank?
        user_track_measures = user_track_measures.where(" measures.made_at <= ? ", endt.to_s(:db))  unless endt.blank?
        #db
        user_track_measures = user_track_measures.where(" measures.loudness >= ?", query_start_db) unless query_start_db.blank?
        user_track_measures = user_track_measures.where(" measures.loudness <= ?", query_end_db) unless query_end_db.blank?

        query_result << user_track_measures.all unless user_track_measures.nil?  #execute
      }

      #.map(&:inspect).join(', ')
    } unless users.blank?

    # tidy up the measures
    query_result.select!{ |m| !m.nil? and !m.blank?}
    #flatten
    query_result.flatten!

    query_result

  end

  # gets a city's measurements within specified times. used in custommaps, but removed bcz cityid is not good for
  #  tracking measures
  # @param [Object] cityid
  # @param [Object] start_time
  # @param [Object] end_time
  def execcitymeasures(cityid, start_time, end_time)
    tracks =  Track.where(:city_id => cityid)
    startt = DateTime.strptime(start_time, DATE_TIME_FORMAT)
    endt = DateTime.strptime(end_time, DATE_TIME_FORMAT)
    measures = []
    tracks.each{ |track|
      user_track_measures = track.measures.scoped
      #time
      user_track_measures = user_track_measures.where(" measures.made_at >= ? ", startt.to_s(:db))  unless startt.blank?
      user_track_measures = user_track_measures.where(" measures.made_at <= ? ", endt.to_s(:db))  unless endt.blank?

      measures  << user_track_measures.all
    }
    # tidy up the measures
    measures.select!{ |m| !m.nil? and !m.blank?}
    #flatten
    measures.flatten!

    measures
  end

end
